import os
import shutil
import requests
import time # 新增导入
from datetime import datetime
from gradio_client import Client, handle_file, exceptions as gradio_exceptions # 导入gradio异常
import cv2 # 导入 OpenCV
import requests.exceptions # 显式导入requests的异常模块

from config import (
    OUTPUT_DIR, AUDIO_GEN_PORT, VIDEO_GEN_PORT, WECHAT_WEBHOOK_URL,
    INTERNAL_UPLOAD_URL, EXTERNAL_UPLOAD_URL,
    INTERNAL_NOTIFY_STATUS_URL_TEMPLATE, EXTERNAL_NOTIFY_STATUS_URL_TEMPLATE,
    TASK_STATUS_PROCESSING, TASK_STATUS_FAILED # 导入状态码常量
)
from logger_setup import logger
from utils import get_filepath_from_gradio_output, cleanup_local_files, ServerTaskFailedError, format_text_for_speech

def send_status_notification(task_id: str, status: int, message: str, is_internal_request: bool):
    """
    调用config.py中配置的PUT通知接口，根据传入的 status 设置状态。
    Args:
        task_id: 任务的唯一标识符。
        status: 任务的当前状态码 (例如：2 表示正在合成, 3 表示失败)。
        message: 附加的通知消息。
        is_internal_request: 布尔值，指示请求是否来自内部网络。
    """
 
    if is_internal_request:
        notify_url_template = INTERNAL_NOTIFY_STATUS_URL_TEMPLATE
    else:
        notify_url_template = EXTERNAL_NOTIFY_STATUS_URL_TEMPLATE

    # 根据任务状态码决定 statusMsg 的内容
    status_msg_content = message if status == TASK_STATUS_FAILED else ""
    notify_url = notify_url_template.format(task_id=task_id, status=status, statusMsg=status_msg_content)

    try:
        response = requests.put(notify_url, timeout=10)
        response.raise_for_status()
        logger.info(f"状态通知已发送至 {notify_url} 成功: 任务ID {task_id}, 状态码 {status}, 消息: {message}, 响应: {response.text}")
    except requests.exceptions.RequestException as request_exception:
        logger.error(f"发送状态通知至 {notify_url} 失败: 任务ID {task_id}, 状态码 {status}, 消息: {message}, 错误: {request_exception}")
    except Exception as unknown_status_notification_exception:
        logger.error(f"发送状态通知时发生未知错误: 任务ID {task_id}, 状态码 {status}, 消息: {message}, 错误: {unknown_status_notification_exception}", exc_info=True)

def send_wechat_notification(task_id, name, status, info, file_path=None, error_reason=None, video_upload_url=None):
    """
    发送企业微信机器人通知，使用文本格式
    
    Args:
        task_id: 任务ID
        name: 任务名称
        status: 'success'或'fail'
        info: 任务说明信息
        file_path: 输出文件路径（成功时）
        error_reason: 错误原因（失败时）
        video_upload_url: 视频上传成功后的URL（成功时）
    """
    now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")  # 当前时间格式化
    
    # 构建文本消息内容
    text_content = f"任务通知 (ID: {task_id})\n"
    text_content += f"任务名称: {name}\n"
    text_content += f"时间: {now}\n"
    text_content += f"说明: {info}\n"

    # 根据任务状态添加不同内容
    if status == 'success':
        text_content += "状态: ✅ 成功\n"
        if file_path:
            file_name_display = os.path.basename(file_path)
            text_content += f"输出文件: {file_name_display}"
        else:
            text_content += "输出文件: -"
        if video_upload_url:  # 如果视频URL存在，则添加
            text_content += f"\n视频URL：{video_upload_url}"
    else:  # fail
        text_content += "状态: ❌ 失败\n"
        if error_reason:
            text_content += f"错误原因: {error_reason}"
        else:
            text_content += "错误原因: -"

    # 构建请求负载
    payload = {
        "msgtype": "text",
        "text": {
            "content": text_content
        }
    }

    # 发送请求
    try:
        resp = requests.post(WECHAT_WEBHOOK_URL, json=payload, timeout=10)
        resp.raise_for_status()  # 检查HTTP响应状态
        response_json = resp.json()
        # 检查API返回状态
        if response_json.get("errcode") != 0:
            logger.error(f"企业微信API返回错误: {response_json.get('errmsg')} (errcode: {response_json.get('errcode')}), 任务ID: {task_id}, 请求体: {payload}")
        else:
            logger.info(f"企业微信通知已成功发送: {status}, 任务ID: {task_id}")
    except requests.exceptions.RequestException as request_exception:
        logger.error(f"企业微信通知发送请求失败: {request_exception}, 任务ID: {task_id}, 请求体: {payload}")
    except Exception as unknown_wechat_notification_exception:
        logger.error(f"企业微信通知发送过程中发生未知错误: {unknown_wechat_notification_exception}, 任务ID: {task_id}, 请求体: {payload}")

def process_notification_background(data, server_ip):
    """
    核心任务处理函数，负责音视频生成和上传流程
    
    Args:
        data: 任务数据字典，包含任务ID、名称、文本、音视频URL等信息
        server_ip: 分配的处理服务器IP
    """
    # 根据分配的服务器IP构建API URL
    current_audio_gen_api_url = f"http://{server_ip}:{AUDIO_GEN_PORT}/"
    current_video_gen_api_url = f"http://{server_ip}:{VIDEO_GEN_PORT}/"

    # 获取任务基本信息
    task_id = data.get("id", "未知ID")
    original_name = data.get("name", "未知名称")
    name = str(original_name).replace(" ", "_").replace("/", "_").replace("\\", "_")  # 安全处理文件名
    
    # 定义本地文件路径，文件名包含任务ID
    target_audio_filename = f"{task_id}-合成-{name}.wav"
    target_audio_filepath = os.path.join(OUTPUT_DIR, target_audio_filename)
    target_video_filename = f"{task_id}-合成-{name}.mp4"
    target_video_filepath = os.path.join(OUTPUT_DIR, target_video_filename)
    
    local_files_to_cleanup = []
    should_cleanup_files = False # 控制finally块中的清理逻辑

    # 根据请求来源确定上传URL
    is_internal_request = data.get('is_internal_request', False)
    current_upload_url = INTERNAL_UPLOAD_URL if is_internal_request else EXTERNAL_UPLOAD_URL
    logger.info(f"[后台任务 ID: {task_id} on {server_ip}] 使用上传URL: {current_upload_url} (内部请求: {is_internal_request})")

    try:
        # 提取请求中的必要数据
        video_url = data["video_url"]  # 原始视频URL
        audio_url_prompt = data["audio_url"]  # 音频提示URL
        original_text_prompt = data["text"]  # 原始文本

        logger.info(f"[后台任务 ID: {task_id} on {server_ip}] 开始处理通知, 名称: {name}")
        logger.info(f"[后台任务 ID: {task_id} on {server_ip}] 原始文本: '{original_text_prompt}'")

        # 1. 文本预处理 - 使用新的格式化函数
        text_prompt_for_audio = format_text_for_speech(original_text_prompt)
        logger.info(f"[后台任务 ID: {task_id} on {server_ip}] 格式化后用于音频生成的文本: '{text_prompt_for_audio}'")

        # --- 2. 音频生成 ---
        logger.info(f"[后台任务 ID: {task_id} on {server_ip}] 步骤2.1: 开始为 '{name}' 生成音频...")
        try:
            audio_client = Client(current_audio_gen_api_url, ssl_verify=False)
            temp_audio_output = audio_client.predict(
                prompt=handle_file(audio_url_prompt), text=text_prompt_for_audio,
                infer_mode="批次推理", api_name="/gen_single",
            )
            temp_audio_output_path = get_filepath_from_gradio_output(temp_audio_output)
            if not temp_audio_output_path:
                raise ValueError("音频生成返回路径无效")
            shutil.move(temp_audio_output_path, target_audio_filepath)
            local_files_to_cleanup.append(target_audio_filepath)
            logger.info(f"[后台任务 ID: {task_id} on {server_ip}] 音频成功生成并本地保存至: {target_audio_filepath}")
        except ValueError as audio_value_error:
            if "could not fetch config" in str(audio_value_error).lower() or "connection refused" in str(audio_value_error).lower():
                raise ServerTaskFailedError(f"音频Gradio客户端连接失败 on {server_ip}", original_exception=audio_value_error)
            else:
                error_msg = f'音频源错误 (服务器: {server_ip})'
                logger.error(f"[后台任务 ID: {task_id} on {server_ip}] 音频生成过程中发生参数或配置错误 for '{name}': {audio_value_error}", exc_info=True)
                send_wechat_notification(task_id, name, 'fail', error_msg, error_reason=str(audio_value_error))
                send_status_notification(task_id, TASK_STATUS_FAILED, error_msg, is_internal_request)
                should_cleanup_files = True
                return
        except gradio_exceptions.AppError as audio_gradio_app_error:
            error_msg = f'音频生成Gradio App错误 (服务器: {server_ip})'
            logger.error(f"[后台任务 ID: {task_id} on {server_ip}] 音频生成过程中发生Gradio App错误 for '{name}': {audio_gradio_app_error}", exc_info=True)
            send_wechat_notification(task_id, name, 'fail', error_msg, error_reason=str(audio_gradio_app_error))
            send_status_notification(task_id, TASK_STATUS_FAILED, error_msg, is_internal_request)
            should_cleanup_files = True
            return
        except Exception as audio_processing_exception:
            logger.error(f"[后台任务 ID: {task_id} on {server_ip}] 音频生成过程中发生未知或连接类错误 for '{name}': {audio_processing_exception}", exc_info=True)
            raise ServerTaskFailedError(f"音频生成执行时发生意外错误 on {server_ip}", original_exception=audio_processing_exception)

        # --- 3. 视频生成 ---
        logger.info(f"[后台任务 ID: {task_id} on {server_ip}] 步骤2.2: 开始为 '{name}' 生成视频...")
        try:
            video_client = Client(current_video_gen_api_url, ssl_verify=False)
            temp_video_output = video_client.predict(
                video_file={"video": handle_file(video_url)}, audio_file=handle_file(target_audio_filepath),
                face_hd=True, api_name="/process_video",
            )
            temp_video_output_path = get_filepath_from_gradio_output(temp_video_output)
            if not temp_video_output_path:
                raise ValueError("视频生成返回路径无效")
            shutil.move(temp_video_output_path, target_video_filepath)
            local_files_to_cleanup.append(target_video_filepath)
            logger.info(f"[后台任务 ID: {task_id} on {server_ip}] 视频成功生成并本地保存至: {target_video_filepath}")
        except ValueError as video_value_error:
            if "could not fetch config" in str(video_value_error).lower() or "connection refused" in str(video_value_error).lower():
                raise ServerTaskFailedError(f"视频Gradio客户端连接失败 on {server_ip}", original_exception=video_value_error)
            else:
                error_msg = f'视频源错误 (服务器: {server_ip})'
                logger.error(f"[后台任务 ID: {task_id} on {server_ip}] 视频生成过程中发生参数或配置错误 for '{name}': {video_value_error}", exc_info=True)
                send_wechat_notification(task_id, name, 'fail', error_msg, error_reason=str(video_value_error))
                send_status_notification(task_id, TASK_STATUS_FAILED, error_msg, is_internal_request)
                should_cleanup_files = True
                return
        except gradio_exceptions.AppError as video_gradio_app_error:
            error_msg = f'视频生成Gradio App错误 (服务器: {server_ip})'
            logger.error(f"[后台任务 ID: {task_id} on {server_ip}] 视频生成过程中发生Gradio App错误 for '{name}': {video_gradio_app_error}", exc_info=True)
            send_wechat_notification(task_id, name, 'fail', error_msg, error_reason=str(video_gradio_app_error))
            send_status_notification(task_id, TASK_STATUS_FAILED, error_msg, is_internal_request)
            should_cleanup_files = True
            return
        except Exception as video_processing_exception:
            logger.error(f"[后台任务 ID: {task_id} on {server_ip}] 视频生成过程中发生未知或连接类错误 for '{name}': {video_processing_exception}", exc_info=True)
            raise ServerTaskFailedError(f"视频生成执行时发生意外错误 on {server_ip}", original_exception=video_processing_exception)

        # --- 4. 文件上传 ---
        uploaded_video_url = None
        try:
            upload_files_metadata = [
                (target_audio_filepath, "音频"),
                (target_video_filepath, "视频")
            ]
            for file_path, file_type in upload_files_metadata:
                if not (file_path and os.path.exists(file_path)):
                    logger.warning(f"[文件上传 ID: {task_id} on {server_ip}] {file_type}文件路径 '{file_path}' 无效或文件不存在，跳过上传。")
                    if file_type == "视频": # 如果本应上传的视频文件无效，则认为上传链失败
                         raise FileNotFoundError(f"{file_type}文件 '{file_path}' 未找到，无法继续上传流程。")
                    continue # 音频文件不存在，可以跳过，但视频是关键

                logger.info(f"[后台任务 ID: {task_id} on {server_ip}] 开始上传{file_type}文件: '{file_path}'")
                with open(file_path, 'rb') as file_obj:
                    files_payload = {'file': (os.path.basename(file_path), file_obj)}
                    data_payload = {
                        'assetId': str(task_id),
                        'text': text_prompt_for_audio
                    }
                    if file_type == "视频":
                        try:
                            cap = cv2.VideoCapture(file_path)
                            if cap.isOpened():
                                frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
                                fps = cap.get(cv2.CAP_PROP_FPS)
                                if fps > 0:
                                    duration = frame_count / fps
                                    data_payload['videoDuration'] = str(int(duration))
                                    logger.info(f"[文件上传 ID: {task_id} on {server_ip}] 获取到视频时长: {duration}秒")
                                else: logger.warning(f"[文件上传 ID: {task_id}] 视频FPS为0: {file_path}")
                                cap.release()
                            else: logger.warning(f"[文件上传 ID: {task_id}] 无法打开视频: {file_path}")
                        except Exception as duration_calculation_exception:
                            logger.warning(f"[文件上传 ID: {task_id}] 获取视频时长失败: {duration_calculation_exception}")
                    
                    max_retries = 3
                    retry_delay = 5 
                    for attempt in range(max_retries):
                        try:
                            response = requests.post(current_upload_url, files=files_payload, data=data_payload, timeout=30)
                            response.raise_for_status()
                            logger.info(f"[文件上传 ID: {task_id}] {file_type}上传成功 (尝试 {attempt + 1})")
                            if file_type == "视频":
                                try:
                                    response_data = response.json()
                                    if response_data.get("code") == 200 and response_data.get("data") and response_data["data"].get("url"):
                                        uploaded_video_url = response_data["data"]["url"]
                                        logger.info(f"[文件上传 ID: {task_id}] 提取到视频URL: {uploaded_video_url}")
                                except ValueError:
                                    logger.warning(f"[文件上传 ID: {task_id}] 解析视频上传响应JSON失败: {response.text}")
                            break 
                        except requests.exceptions.RequestException as upload_attempt_exception:
                            logger.warning(f"[文件上传 ID: {task_id}] {file_type}上传尝试 {attempt + 1}/{max_retries} 失败: {upload_attempt_exception}")
                            if attempt < max_retries - 1:
                                time.sleep(retry_delay)
                            else:
                                logger.error(f"[文件上传 ID: {task_id}] {file_type}上传在 {max_retries} 次尝试后失败。")
                                raise # 重新抛出，由外层捕获为 ServerTaskFailedError
        except FileNotFoundError as file_not_found_error:
            logger.error(f"[后台任务 ID: {task_id} on {server_ip}] 文件上传前置检查失败 (文件未找到): {file_not_found_error}", exc_info=True)
            send_wechat_notification(task_id, name, 'fail', f'文件准备上传时未找到 (服务器: {server_ip})', error_reason=str(file_not_found_error))
            should_cleanup_files = True # 标记清理，因为是不可重试的内部错误
            return # 直接返回，不再尝试其他操作
        except requests.exceptions.RequestException as final_upload_exception:
            logger.error(f"[文件上传 ID: {task_id} on {server_ip}] 文件上传最终失败: {final_upload_exception}", exc_info=True)
            raise ServerTaskFailedError(f"文件上传在所有重试后失败 on {server_ip}", original_exception=final_upload_exception)
        except Exception as generic_upload_exception:
            logger.error(f"[文件上传 ID: {task_id} on {server_ip}] 文件上传过程中发生其他未知错误: {generic_upload_exception}", exc_info=True)
            raise ServerTaskFailedError(f"文件上传过程中发生其他未知错误 on {server_ip}", original_exception=generic_upload_exception)

        # --- 5. 成功处理完毕 ---
        logger.info(f"[后台任务 ID: {task_id} on {server_ip}] 成功处理通知, 名称: {name}")
        send_wechat_notification(task_id, name, 'success', 
                                 f'任务已顺利完成 (处理服务器: {server_ip})', 
                                 file_path=target_video_filepath, 
                                 video_upload_url=uploaded_video_url)
        should_cleanup_files = True

    except ServerTaskFailedError:
        # 对于可重试错误，不在此处设置 should_cleanup_files = True，由 finally 块的默认行为处理（即不清理）
        raise 
    except KeyError as key_error:
        logger.warning(f"[后台任务 ID: {task_id} on {server_ip}] 请求JSON中缺少键: {key_error} for data: {data}")
        send_wechat_notification(task_id, name, 'fail', f'请求JSON缺少关键字段 (处理服务器: {server_ip})', error_reason=str(key_error))
        should_cleanup_files = True
    except Exception as unknown_background_processing_exception:
        logger.error(f"[后台任务 ID: {task_id} on {server_ip}] 处理通知时发生无法重试的未知错误: {unknown_background_processing_exception}", exc_info=True)
        send_wechat_notification(task_id, name, 'fail', f'处理通知发生未知异常 (处理服务器: {server_ip})', error_reason=str(unknown_background_processing_exception))
        should_cleanup_files = True
    finally:
        if should_cleanup_files and local_files_to_cleanup:
            logger.info(f"[后台任务 ID: {task_id} on {server_ip}] 执行本地文件清理。应清理状态: True, 文件: {local_files_to_cleanup}")
            cleanup_local_files(local_files_to_cleanup)
            logger.info(f"[后台任务 ID: {task_id} on {server_ip}] 本地临时文件已清理完毕 for '{name}'")
        elif not should_cleanup_files and local_files_to_cleanup:
             logger.info(f"[后台任务 ID: {task_id} on {server_ip}] 跳过本地文件清理 (可能因可重试错误)。应清理状态: False, 文件: {local_files_to_cleanup}")

def process_server_task(data, server_ip):
    """包装 process_notification_background 以便在线程中执行并处理特定异常"""
    task_id = data.get("id", "未知ID")
    name = data.get("name", "未知名称")
    is_internal_request = data.get('is_internal_request', False)
    try:
        # 在调用 process_notification_background 之前发送"正在合成"状态通知
        send_status_notification(task_id, TASK_STATUS_PROCESSING, f"任务开始处理，状态：正在合成 (服务器: {server_ip})", is_internal_request)
        process_notification_background(data, server_ip)
    except ServerTaskFailedError as server_task_failed_error:
        error_message = f"服务器 {server_task_failed_error.server_ip} 处理任务 '{name}' (ID: {task_id}) 失败: {server_task_failed_error.message}"
        logger.error(error_message, exc_info=True) # 记录原始异常信息
        send_wechat_notification(task_id, name, 'fail', error_message, error_reason=str(server_task_failed_error.original_exception or server_task_failed_error))
        send_status_notification(task_id, TASK_STATUS_FAILED, error_message, is_internal_request)
    except Exception as unknown_server_task_exception:
        # 捕获所有其他未预料的异常
        error_message = f"处理任务 '{name}' (ID: {task_id}) 时发生全局未知错误 on {server_ip}: {unknown_server_task_exception}"
        logger.error(error_message, exc_info=True)
        send_wechat_notification(task_id, name, 'fail', error_message, error_reason=str(unknown_server_task_exception))
        send_status_notification(task_id, TASK_STATUS_FAILED, error_message, is_internal_request)